define([
    'libs/leaflet/Polyline.encoded',
    'libs/leaflet/KML',
], function (_EncodedPolylines, _KMLLayers) {

    var view = Backbone.View.extend({
        maxPointsToEdit: 500,
        distanceToEnd: 100,
        selectedLayersForUnion: [],
        toolTipDistance: undefined,
        linksToDragOnMarker: [],
        latlngForMagicRoute: undefined,
        layerForMagicRoute: undefined,
        disableMap: function (map) {
            if (map) {
                map.dragging.disable();
                map.touchZoom.disable();
                map.doubleClickZoom.disable();
                map.scrollWheelZoom.disable();
                map.boxZoom.disable();
                map.keyboard.disable();
            }
        },
        enableMap: function (map) {
            if (map) {
                map.dragging.enable();
                map.touchZoom.enable();
                map.doubleClickZoom.enable();
                map.scrollWheelZoom.enable();
                map.boxZoom.enable();
                map.keyboard.enable();
            }
        },
        // get coords for marker or links near of an latlng
        getNearObjectCoords: function (latlng, workLayer, findLinks) {
            if (!workLayer)
                return;

            var minorDistance = Infinity;
            var selectedLatLng = undefined;

            workLayer.eachLayer(function (e) {

                var layer = e.layer || e.target || e;
                var isMarker = layer instanceof L.Marker;

                if (findLinks) {

                    if (!isMarker) {

                        if (minorDistance > latlng.distanceTo(layer.getLatLngs()[0])) {
                            minorDistance = latlng.distanceTo(layer.getLatLngs()[0]);
                            selectedLatLng = layer.getLatLngs()[0];
                        }


                        if (minorDistance > latlng.distanceTo(layer.getLatLngs()[layer.getLatLngs().length - 1])) {
                            minorDistance = latlng.distanceTo(layer.getLatLngs()[layer.getLatLngs().length - 1]);
                            selectedLatLng = layer.getLatLngs()[layer.getLatLngs().length - 1];
                        }
                    }
                }
                else {
                    if (isMarker) {
                        if (minorDistance > latlng.distanceTo(layer._latlng)) {
                            minorDistance = latlng.distanceTo(layer._latlng);
                            selectedLatLng = layer._latlng;
                        }
                    }
                }
            });

            return selectedLatLng;
        },

        // enable/disable markers/links for edit 
        editMode: function (edit, workLayer, mapView) {
            if (!workLayer)
                return;

            var _this = this;

            workLayer.eachLayer(function (e) {

                var layer = e.layer || e.target || e;
                var isMarker = layer instanceof L.Marker;

                // Don't do anything if this layer is a marker but doesn't have an icon. Markers
                // should usually have icons. If using Leaflet.draw with Leafler.markercluster there
                // is a chance that a marker doesn't.
                if (isMarker && !layer._icon) {
                    return;
                }

                if (edit) {
                    if (isMarker) {
                        layer.dragging.enable();

                        layer.on("dragend", function () {
                            _this.endDragLinks();
                            mapView.validateIntegrity(true);
                            mapView.changeModeOn();
                        });

                    } else {
                        /*
                        if (layer.getLatLngs().length < _this.maxPointsToEdit)
                            layer.editing.enable();
                        */

                        layer.on("edit", function () {
                            mapView.validateIntegrity(true);
                            mapView.changeModeOn();
                            layer.options.isDirty = true;
                        });
                    }


                }
                else {
                    if (isMarker) {
                        layer.dragging.disable();
                        layer.off("dragend");
                    } else {
                        layer.editing.disable();
                        layer.off("edit");
                    }

                }

            });

        },
        getDistanceInMeters: function (coords) {
            var total = 0;
            for (var i = 0, n = coords.length - 1; i < n; i++) {
                total += coords[i].distanceTo(coords[i + 1]);
            }
            total = Math.round(total * 10) / 10;
            return total;
        },
        // get full distans of polyline
        getFullDistance: function (coords) {
            var total = this.getDistanceInMeters(coords);
            var totalRound;
            if (total < 1000)
                totalRound = total + " m";
            else {
                total = total / 1000;
                total = Math.round(total * 10) / 10;
                totalRound = total + " km";
            }
            return totalRound;
        },
        // display layer distance over polilyne
        displayDistance: function (e, map) {
            if (e.layer && e.layer.hasOwnProperty("_latlngs") && e.layer._latlngs.length > 1) {

                var position = e.latlng;
                // create tooltip
                var icon = L.divIcon({
                    className: 'leaflet-measure-tooltip',
                    iconAnchor: [20, 20]
                });
                this.toolTipDistance = L.marker(position, {
                    icon: icon,
                    clickable: false
                }).addTo(map);

                var totalRound = this.getFullDistance(e.layer._latlngs);

                var text = '<div class="leaflet-measure-tooltip-total">' + totalRound + '</div>';
                this.toolTipDistance._icon.innerHTML = text;
            }
        },
        // hide layer distance
        hideDistance: function (e, map) {
            if (this.toolTipDistance != undefined) {
                map.removeLayer(this.toolTipDistance);
                this.toolTipDistance = undefined;
            }
        },
        // split polyline (intento de...), return array of latlngs
        splitPolyline: function (map, latlngs, latlng) {
            if (latlngs.length > 1) {
                var minDistance = Infinity;
                var selectedSegment = -1;
                var dist;

                for (nn = 1; nn < latlngs.length; nn++) {
                    dist = this.distanceSegment(map, latlng, latlngs[nn - 1], latlngs[nn]);
                    if (minDistance > dist) {
                        minDistance = dist;
                        selectedSegment = nn - 1;
                    }
                }
                if (selectedSegment >= 0) {

                    var path1 = [];
                    var path2 = [];
                    var auxPath = path1;
                    for (nn = 0; nn < latlngs.length; nn++) {
                        auxPath.push(new L.latLng(latlngs[nn].lat, latlngs[nn].lng));

                        if (nn >= selectedSegment) {

                            var cutOnLatLng = this.closestOnSegment(map, latlng, latlngs[selectedSegment], latlngs[selectedSegment + 1]);

                            auxPath.push(this.cloneLatLns(cutOnLatLng));
                            selectedSegment = Infinity;
                            auxPath = path2;
                            auxPath.push(this.cloneLatLns(cutOnLatLng));


                        }
                    }

                    return [path1, path2];
                }
            }

        },
        // return distance between a {L.LatLng} and a segment (A-B)
        distanceSegment: function (map, latlng, latlngA, latlngB) {
            var p = map.latLngToLayerPoint(latlng),
               p1 = map.latLngToLayerPoint(latlngA),
               p2 = map.latLngToLayerPoint(latlngB);
            return L.LineUtil.pointToSegmentDistance(p, p1, p2);
        },
        //return latlng of segment , near to  latlng 
        closestOnSegment: function (map, latlng, latlngA, latlngB) {
            var maxzoom = map.getMaxZoom();
            if (maxzoom === Infinity)
                maxzoom = map.getZoom();
            var p = map.project(latlng, maxzoom),
                p1 = map.project(latlngA, maxzoom),
                p2 = map.project(latlngB, maxzoom),
                closest = L.LineUtil.closestPointOnSegment(p, p1, p2);
            return map.unproject(closest, maxzoom);
        },
        // clear selected layers for union
        clearUnionLayers: function () {

            if (this.selectedLayersForUnion.length > 0) {
                delete this.selectedLayersForUnion[0];
            }
            this.selectedLayersForUnion = [];

        },
        // perform union selected and clicked layer
        unionLayers: function (workLayer, layer, mapView) {


            if (layer && layer.hasOwnProperty("_latlngs")) {


                if (this.selectedLayersForUnion.length == 0) {
                    this.selectedLayersForUnion.push(layer);
                }
                else {

                    if (layer == this.selectedLayersForUnion[0])
                        return;


                    var arr1 = this.selectedLayersForUnion[0]._latlngs;
                    var arr2 = layer._latlngs;

                    var distance = Infinity;
                    var option = 1;


                    // check for near points
                    // simple, no action
                    var latlngStart = arr1[arr1.length - 1];
                    var latlngEnd = arr2[0];
                    if (latlngStart.distanceTo(latlngEnd) < distance) {
                        distance = latlngStart.distanceTo(latlngEnd);
                        option = 1;
                    }

                    // opt 2
                    latlngStart = arr1[arr1.length - 1];
                    latlngEnd = arr2[arr2.length - 1];
                    if (latlngStart.distanceTo(latlngEnd) < distance) {
                        distance = latlngStart.distanceTo(latlngEnd);
                        option = 2;
                    }

                    // opt 3
                    latlngStart = arr1[0];
                    latlngEnd = arr2[0];
                    if (latlngStart.distanceTo(latlngEnd) < distance) {
                        distance = latlngStart.distanceTo(latlngEnd);
                        option = 3;
                    }

                    // opt 4
                    latlngStart = arr1[0];
                    latlngEnd = arr2[arr2.length - 1];
                    if (latlngStart.distanceTo(latlngEnd) < distance) {
                        distance = latlngStart.distanceTo(latlngEnd);
                        option = 4;

                    }


                    // perform join
                    var result;
                    switch (option) {
                        case 1:
                            result = arr1.concat(arr2);
                            break;

                        case 2:
                            arr2 = arr2.reverse();
                            result = arr1.concat(arr2);
                            break;

                        case 3:
                            arr1 = arr1.reverse();
                            result = arr1.concat(arr2);
                            break;

                        case 4:
                            arr1 = arr1.reverse();
                            arr2 = arr2.reverse();
                            result = arr1.concat(arr2);
                            break;
                    }



                    var full = L.polyline(result, layer.options);
                    workLayer.addLayer(full);
                    full.options.isDirty = true;

                    mapView.giView.removeLayerFromMap(this.selectedLayersForUnion[0]._leaflet_id);
                    mapView.giView.removeLayerFromMap(layer._leaflet_id);

                    if (this.selectedLayersForUnion[0].options.entityId) {
                        mapView.deletedShapes.push(this.selectedLayersForUnion[0].options.entityId);
                    }
                    if (layer.options.entityId) {
                        mapView.deletedShapes.push(layer.options.entityId);
                    }

                    workLayer.removeLayer(this.selectedLayersForUnion[0]);
                    workLayer.removeLayer(layer);

                    this.clearUnionLayers();
                    this.unionLayers(workLayer, full);
                    full.options.entityId = null;
                    return full;
                }
            }
        },
        // find links to drag when drag marker
        findLinksToDrag: function (workLayer, latlng) {
            var _this = this;
            var tolerance = 10;
            this.linksToDragOnMarker = [];
            workLayer.eachLayer(function (e) {

                var layer = e.layer || e.target || e;
                var isLink = layer instanceof L.Polyline;

                if (isLink) {
                    if (tolerance > latlng.distanceTo(layer._latlngs[0])) {
                        _this.linksToDragOnMarker.push({
                            layer: layer,
                            pos: 0
                        });
                        layer.editing.disable();
                    }
                    if (tolerance > latlng.distanceTo(layer._latlngs[layer._latlngs.length - 1])) {
                        _this.linksToDragOnMarker.push({
                            layer: layer,
                            pos: layer._latlngs.length - 1
                        });
                        if (layer.getLatLngs().length < this.maxPointsToEdit)
                            layer.editing.disable();
                    }
                }
            });



        },
        // drag links on drag marker
        performDragLinks: function (worklayer, latlng) {
            var _this = this;
            $(this.linksToDragOnMarker).each(function (i, item) {
                var arr = item.layer.getLatLngs();
                arr[item.pos] = _this.cloneLatLns(latlng);
                item.layer.redraw();
                item.layer.options.isDirty = true;
            });
        },
        // end drag links
        endDragLinks: function () {
            $(this.linksToDragOnMarker).each(function (i, item) {
                if (item.layer.getLatLngs().length < this.maxPointsToEdit)
                    item.layer.editing.enable();
            });

            this.linksToDragOnMarker = [];
        },
        // Clone LatLns object
        cloneLatLns: function (latlng) {
            return new L.latLng(latlng.lat, latlng.lng);
        },
        // clear previous coord for magic route
        clearMagicRoute: function () {
            this.latlngForMagicRoute = undefined;
            this.layerForMagicRoute = undefined;
        },
        drawMagicRoute: function (map, workLayer, latlng, isMarker, callback) {
            if (workLayer && latlng) {

                if (this.latlngForMagicRoute == undefined) {  // first click{
                    this.latlngForMagicRoute = latlng;
                }
                else if (this.latlngForMagicRoute.distanceTo(latlng) > 4) {


                    // get magic route
                    var _this = this;
                    var url = "http://router.project-osrm.org/viaroute?loc=" + this.latlngForMagicRoute.lat + "," + this.latlngForMagicRoute.lng + "&loc=" + latlng.lat + "," + latlng.lng;
                    url += "&alt=false"

                    // get max definition of points
                    //url += "&z=" + map.getZoom();
                    $.ajax(url, { dataType: "jsonp", jsonp: "jsonp", cache: true }).success(function (data) {

                        if (data.route_geometry) {

                            var encodedData = data.route_geometry;
                            var arr = L.PolylineUtil.decode(encodedData, 6);

                            if (arr) {

                                if (_this.layerForMagicRoute != undefined) {

                                    if (isMarker) {
                                        arr.push(latlng);
                                    }
                                    $(arr).each(function (i, item) {
                                        _this.layerForMagicRoute.addLatLng(item);
                                    });
                                    _this.layerForMagicRoute.redraw();
                                }
                                else {

                                    if (isMarker) {
                                        arr.unshift(_this.latlngForMagicRoute);
                                        arr.push(latlng);
                                    }

                                    _this.layerForMagicRoute = new L.polyline(arr, {
                                        color: __mapColors['POLYLINE'],
                                        weight: 6,
                                        clickable: true,
                                        className: 'POLYLINE'
                                    });
                                    _this.layerForMagicRoute.options.isDirty = true;
                                    workLayer.addLayer(_this.layerForMagicRoute);
                                }

                                _this.latlngForMagicRoute = latlng;
                                if (isMarker)
                                    _this.layerForMagicRoute = undefined;
                            }

                            if (callback != undefined)
                                callback();

                        }

                    });

                }
            }
        },
        // Add an KML Layer to map into layerGroup
        addKMLLayer: function (map, layerGroup, layerName, layerUrl, callbackOnLoad) {

            var track = new L.KML(layerUrl, { async: true });
            track.on("loaded", function (e) {
                map.fitBounds(e.target.getBounds());
                $("#mainLoading").hide();

                if (callbackOnLoad != undefined) {
                    callbackOnLoad(e)
                }

            });
            track.on("error", function (e) {
                $("#mainLoading").hide();
            });
            layerGroup.addLayer(track);
            $("#mainLoading").show();

            return track;

        },
        moveToLocation: function (map, qry, mapView) {

            var geocoder = new google.maps.Geocoder();

            var parameters = {
                address: qry
            };

            var results = geocoder.geocode(parameters, function (data) {
                data = { results: data };

                if (data.results.length > 0) {
                    var lat = data.results[0].geometry.location.lat();
                    var lng = data.results[0].geometry.location.lng();
                    var name = qry;
                    if (data.results[0].address_components && data.results[0].address_components.length > 0) {
                        name = data.results[0].address_components[0].short_name;
                    }

                    map.setView([lat, lng]);
                    var marker_id = mapView.addMarker([lat, lng], undefined, 'MARKER', undefined, true)._leaflet_id;
                    mapView.changeModeOn();
                    mapView.addShapeName(marker_id, name);
                    mapView.showInputName(marker_id);
                }
            });
        },
        simplifyPolyline: function (map, layer, factor) {

            layer.options.smoothFactor = factor;
            layer._simplifyPoints();
            var latlngs = [];
            for (var ii = 0; ii < layer._parts.length; ii++) {
                for (var nn = 0; nn < layer._parts[ii].length; nn++) {
                    latlngs.push(map.layerPointToLatLng(layer._parts[ii][nn]));
                }
            }
            layer.options.smoothFactor = 1;
            layer.setLatLngs(latlngs);
        },
        // return nro of markers in layer
        countMarkers: function (layer) {
            var res = 0;
            layer.eachLayer(function (l) {
                if (l instanceof L.Marker) {
                    res++;
                }
            });
            return res;
        },
        // validate exists nodes (markers) on start and end of an link(poly)
        validateEnds: function (poly, workLayer) {
            var okStart = false;
            var okEnd = false;
            var _this = this;

            workLayer.eachLayer(function (layer) {

                if (layer instanceof L.Marker) {

                    if (!okStart && poly.getLatLngs()[0].distanceTo(layer.getLatLng()) < _this.distanceToEnd) {
                        okStart = true;
                    }
                    if (!okEnd && poly.getLatLngs()[poly.getLatLngs().length - 1].distanceTo(layer.getLatLng()) < _this.distanceToEnd) {
                        okEnd = true;
                    }
                }
            });

            return okStart && okEnd;
        }

    });
    return view;
});